package com.woniuxy.creditsystem.shiro;

import com.woniuxy.creditsystem.common.bean.MenuInfo;
import com.woniuxy.creditsystem.common.bean.RoleInfo;
import com.woniuxy.creditsystem.common.bean.UserInfo;
import com.woniuxy.creditsystem.common.util.TraceUtil;
import com.woniuxy.creditsystem.systemmag.service.IMenuService;
import com.woniuxy.creditsystem.systemmag.service.IRoleService;
import com.woniuxy.creditsystem.systemmag.service.ISystemService;
import com.woniuxy.creditsystem.systemmag.service.IUserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.context.annotation.Lazy;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * shiro框架中，用来完成身份认证，授权的对象
 * @author hejie
 */
public class LoginAndAuthRealm extends AuthorizingRealm {

    //    @Autowired//都可以
    @Resource
    @Lazy //加了lazy才不会影响aop的日志记录
    private ISystemService systemServiceImpl;
    @Resource
    @Lazy
    private IUserService userServiceImpl;
    @Resource
    @Lazy
    private IRoleService roleServiceImpl;
    @Resource
    @Lazy
    private IMenuService menuServiceImpl;
    /**
     * 授权方法
     * 需要使用<shiro> | @Shiro相同注解 才能触发
     * @author hejie
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("\n授权开始！");
        //从Session中获得用户
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession();
        UserInfo userBean = (UserInfo) session.getAttribute("user");
        //根据ID查询用户信息，以及角色信息
        System.out.println("userbean="+userBean);
        UserInfo user = userServiceImpl.getOneById(userBean.getId());//-----
        System.out.println("\nin doGetAuthorizationInfo,user="+user);
        RoleInfo roleBean = user.getRoleInfo();
        //定义一个授权信息对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole(roleBean.getRole());//将角色信息装配到授权对象中----???
        //info.addRoles(集合);
       if(roleBean.getId() == 1){//如果角色的ID == 1 说明是：超级管理员
            //查询所有权限
            System.out.println("\nsuperAdmin-----");
            List<MenuInfo> menuBeans= menuServiceImpl.findAll();
            Set<String> permissions= buildSuperPermissions(menuBeans);
            System.out.println("\n打印所有的权限：");
            for (String str:permissions  ) {
               System.out.println(str);
            }
            info.addStringPermissions(permissions);
        }else{
            //根据ID查询 菜单信息，以及功能权限信息
            List<Map> maps = roleServiceImpl.findPermissionByRoleId(roleBean.getId());
            // admin; admin:add; admin:mod; admin:del; admin:find
            // role; role:add; role:mod; role:del; role:find
            Set<String> permissions = buildPermissions(maps);
            System.out.println("\n打印所有的权限：");
            for (String str:permissions  ) {
                System.out.println(str);
            }
            info.addStringPermissions(permissions);//将权限信息装配到授权对象中
        }
        return info;
    }

    /**
     * 身份认证方法
     * 需要在“用户登录系统时触发”
     * @author hejie
     * @param authenticationToken 携带用户名和密码的Token令牌对象
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("\n开始认证！");
        //返回的当前主体令牌中的登录名
        String loginName = (String)authenticationToken.getPrincipal();
        UserInfo userBean = systemServiceImpl.login(loginName);
        System.out.println("\nin doGetAuthenticationInfo,userBean="+userBean);
        if(userBean == null){
            //登录失败，返回null
            System.out.println("\n登录失败,in"+ TraceUtil.positionTrace());
            return null;
        }
        //如果用户存在，则将用户装配到Shiro 的 Session中
        Subject subject = SecurityUtils.getSubject();
        //这个session是shiro自带的session，不是tomcat的httpSession
        Session session = subject.getSession();
        //存储登录数据
        session.setAttribute("user",userBean);

        //所谓盐值加密，就是加密之后，再添加一部分内容，再加密
        //ByteSource  如果使用盐值加密，需要创建ByteSource及对应的info对象。这里的盐值是loginName
        //自定义 盐值类：InitByteSource继承了SimpleByteSource，因为后者没有实现Serializable接口，不能被序列化，即当使用shiro缓存的时候不能使用（）报错：
//        ByteSource byteSource= ByteSource.Util.bytes(loginName.getBytes());//不使用shiro缓存时，才能用它
        InitByteSource byteSource= new InitByteSource(loginName.getBytes());//使用shiro缓存（或者不使用），用它就行

        //返回认证信息 (登录名，密码，盐值，域的名称)（域的名称：哪个对象给你的 令牌，即谁识别的你）
        return new SimpleAuthenticationInfo(loginName, userBean.getPassword(), byteSource, getName());
    }

    /**
     * 构建权限的方法
     * @param maps
     * @return
     */
    private Set<String> buildPermissions(List<Map> maps) {
        Set<String> permissions = new HashSet<>();
        if(!maps.isEmpty()){
            //遍历list集合
            maps.forEach(e -> {
                Map map = e;
                String menuName = (String) map.get("menuName");//取得菜单权限
                String optPerm = (String) map.get("optPerm");//取得操作权限
                //admin; admin:add; admin:mod; admin:del; admin:find;
                permissions.add(menuName+";");
                if(StringUtils.hasLength(optPerm)){
                    if(optPerm.contains(";")){
                        //按照;切割操作权限
                        String[] strs = optPerm.split(";");
                        for(String str:strs){
                            permissions.add(menuName+":"+str+";");
                        }
                    }else{
                        permissions.add(menuName+":"+optPerm+";");
                    }
                }
            });
        }
        return permissions;
    }

    /**
     * 构建超级管理员的权限
     * @param menuBeans
     * @return
     */
    private Set<String> buildSuperPermissions(List<MenuInfo> menuBeans) {
        Set<String> permissions = new HashSet<>();
        menuBeans.forEach(e ->{
            MenuInfo menuBean = e;
            //admin;  role;  type;
            permissions.add(menuBean.getMenuName() + ";");
            //二级菜单才的菜单名称才有权限的意义（在HTML页面用到）
            if(menuBean.getMenuLevel() == 2){
                //admin:add;   admin:mod;
                String optPerm = "add;mod;del;find";
                String[] strs = optPerm.split(";");
                for (String str:strs) {
                    permissions.add(menuBean.getMenuName()+":"+str+";");
                }
            }
        });
        return permissions;
    }
}